/**
 * The MIT License
 *
 * Copyright (c) 2010 Benjamin Klum
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package org.helgoboss.maven_obr_plugin;


import java.io.File;
import java.net.URI;
import java.util.Iterator;
import java.util.List;

import org.apache.felix.bundleplugin.BundleInfo;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.ArtifactCollector;
import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.apache.maven.shared.dependency.tree.DependencyNode;
import org.apache.maven.shared.dependency.tree.DependencyTreeBuilder;
import org.apache.maven.shared.dependency.tree.DependencyTreeBuilderException;
import org.apache.felix.obrplugin.*;
import java.util.regex.Pattern;
import org.apache.maven.artifact.manager.WagonManager;
import java.net.URL;
import java.util.regex.Matcher;
import org.apache.maven.settings.Settings;

/**
 * 
 * 
 * @goal deploy-dependencies
 */
public final class  ObrDeployDependencies extends AbstractMojo
{
    private static final String LS = System.getProperty( "line.separator" );
    private static final int DEPTH = Integer.MAX_VALUE;
    
    
    /**
     * Local OBR Repository.
     * 
     * @parameter expression="${obrRepository}"
     */
    private String obrRepository;


    /**
     * Local Repository.
     * 
     * @parameter expression="${localRepository}"
     * @required
     * @readonly
     */
    private ArtifactRepository localRepository;
    

    /**
     * Optional XML file describing additional requirements and capabilities.
     * 
     * @parameter expression="${obrXml}"
     */
    protected String obrXml;

    /**
     * Remote repositories.
     * 
     * @parameter expression="${project.remoteArtifactRepositories}"
     * @required
     * @readonly
     */
    private List remoteRepositories;

    /**
     * The Maven project.
     * 
     * @parameter expression="${project}"
     * @required
     * @readonly
     */
    private MavenProject project;

    

    /**
     * Ignore missing artifacts that are not required by current project but are required by the
     * transitive dependencies.
     * 
     * @parameter
     */
    private boolean ignoreMissingArtifacts;

    

    /**
     * @component
     */
    private DependencyTreeBuilder m_dependencyTreeBuilder;

    /**
     * @component
     */
    private ArtifactFactory m_factory;

    /**
     * @component
     */
    private ArtifactMetadataSource m_artifactMetadataSource;

    /**
     * @component
     */
    private ArtifactCollector m_collector;
    

    /**
     * Artifact resolver, needed to download jars.
     * 
     * @component
     */
    private ArtifactResolver m_artifactResolver;
    
    
    /* BEGIN REMOTE ADDITION */
    
    /**
     * When true, ignore remote locking.
     * 
     * @parameter expression="${ignoreLock}"
     */
    private boolean ignoreLock;
    
    
    /**
     * Remote OBR Repository.
     * 
     * @parameter expression="${remoteOBR}"
     */
    private String remoteOBR;
    
    
    /**
     * Optional public URL prefix for the remote repository.
     *
     * @parameter expression="${prefixUrl}"
     */
    private String prefixUrl;
    
    /**
     * Alternative deployment repository. Format: id::layout::url
     * 
     * @parameter expression="${altDeploymentRepository}"
     */
    private String altDeploymentRepository;

    /**
     * OBR specific deployment repository. Format: id::layout::url
     * 
     * @parameter expression="${obrDeploymentRepository}"
     */
    private String obrDeploymentRepository;
    
    /**
     * @parameter expression="${project.distributionManagementArtifactRepository}"
     * @readonly
     */
    private ArtifactRepository deploymentRepository;
    
    /**
     * Local Maven settings.
     * 
     * @parameter expression="${settings}"
     * @required
     * @readonly
     */
    private Settings settings;
    
    /**
     * The Wagon manager.
     * 
     * @component
     */
    private WagonManager m_wagonManager;
    
    /* END REMOTE ADDITION */
    

    public void execute() throws MojoExecutionException
    {
        /* BEGIN REMOTE ADDITION */
        
        // if the user doesn't supply an explicit name for the remote OBR file, use the local name instead
        if ( null == remoteOBR || remoteOBR.trim().length() == 0 || "true".equalsIgnoreCase( remoteOBR ) )
        {
            remoteOBR = obrRepository;
        }
        
        
        URI tempURI = ObrUtils.findRepositoryXml( "", remoteOBR );
        String repositoryName = new File( tempURI.getSchemeSpecificPart() ).getName();

        RemoteFileManager remoteFile = new RemoteFileManager( m_wagonManager, settings, getLog() );
        openRepositoryConnection( remoteFile );

        // ======== LOCK REMOTE OBR ========
        getLog().info( "LOCK " + remoteFile + '/' + repositoryName );
        remoteFile.lockFile( repositoryName, ignoreLock );
        File downloadedRepositoryXml = null;
        
        try
        {
            // ======== DOWNLOAD REMOTE OBR ========
            getLog().info( "Downloading " + repositoryName );
            downloadedRepositoryXml = remoteFile.get( repositoryName, ".xml" );


            URI repositoryXml = downloadedRepositoryXml.toURI();

            Config userConfig = new Config();
            userConfig.setRemoteFile( true );
            
            /* END REMOTE ADDITION */
            
            
            ObrUpdate update;

            String mavenRepository = localRepository.getBasedir();

            URI obrXmlFile = ObrUtils.toFileURI( obrXml );

            update = new ObrUpdate( repositoryXml, obrXmlFile, project, mavenRepository, userConfig, getLog() );
            update.parseRepositoryXml();
            
            

            /* Get URLs to the JAR files of all dependencies (also transitive ones) */

            DependencyNode dependencyTree;

            try
            {
                dependencyTree = m_dependencyTreeBuilder.buildDependencyTree( project, localRepository, m_factory,
                    m_artifactMetadataSource, null, m_collector );
            }
            catch ( DependencyTreeBuilderException e )
            {
                throw new MojoExecutionException( "Unable to build dependency tree", e );
            }

            BundleInfo bundleInfo = new BundleInfo();

            getLog().debug( "Will bundle the following dependency tree" + LS + dependencyTree );

            for ( Iterator it = dependencyTree.inverseIterator(); it.hasNext(); )
            {
                DependencyNode node = ( DependencyNode ) it.next();
                if ( !it.hasNext() )
                {
                    /* this is the root, current project */
                    break;
                }

                if ( node.getState() != DependencyNode.INCLUDED )
                {
                    continue;
                }

                if ( Artifact.SCOPE_SYSTEM.equals( node.getArtifact().getScope() ) )
                {
                    getLog().debug( "Ignoring system scoped artifact " + node.getArtifact() );
                    continue;
                }

                Artifact artifact;
                try
                {
                    artifact = resolveArtifact( node.getArtifact() );
                }
                catch ( ArtifactNotFoundException e )
                {
                    if ( ignoreMissingArtifacts )
                    {
                        continue;
                    }

                    throw new MojoExecutionException( "Artifact was not found in the repo" + node.getArtifact(), e );
                }

                node.getArtifact().setFile( artifact.getFile() );

                int nodeDepth = node.getDepth();
                if ( nodeDepth > DEPTH )
                {
                    /* node is deeper than we want */
                    getLog().debug(
                        "Ignoring " + node.getArtifact() + ", depth is " + nodeDepth + ", bigger than " + DEPTH );
                    continue;
                }
                
                /* Update OBR */
                
                
                /* BEGIN REMOTE ADDITION */
                
                if ( prefixUrl != null ) {
                    // support absolute bundle URLs based on given prefix
                    URI bundleJar = ObrUtils.getArtifactURI( localRepository, project.getArtifact() );
                    String relative = ObrUtils.getRelativeURI( ObrUtils.toFileURI( mavenRepository ), bundleJar )
                        .toASCIIString();
                    URL resourceURL = new URL( new URL( prefixUrl + '/' ), relative );
                    URI uri = URI.create( resourceURL.toString() );
                    getLog().info("Computed bundle uri: " + uri);
                    userConfig.setRemoteBundle( uri );
                }
                
                /* END REMOTE ADDITION */
                
                
                File file = node.getArtifact().getFile();
                
                URI bundleJar;

                if ( null == file )
                {
                    bundleJar = ObrUtils.getArtifactURI( localRepository, project.getArtifact() );
                }
                else
                {
                    bundleJar = file.toURI();
                    update.updateRepository( bundleJar, null, null );
                }

            }

            update.writeRepositoryXml();
        } catch ( Exception e ) {
            /* BEGIN REMOTE ADDITION */
            getLog().warn( "Exception while updating remote OBR: " + e.getLocalizedMessage(), e );
        } finally {
            // ======== UNLOCK REMOTE OBR ========
            getLog().info( "UNLOCK " + remoteFile + '/' + repositoryName );
            remoteFile.unlockFile( repositoryName );
            remoteFile.disconnect();

            if ( null != downloadedRepositoryXml )
            {
                downloadedRepositoryXml.delete();
            }
            /* END REMOTE ADDITION */
        }

    }
    

    private Artifact resolveArtifact( Artifact artifact ) throws MojoExecutionException, ArtifactNotFoundException
    {
        VersionRange versionRange;
        if ( artifact.getVersion() != null )
        {
            versionRange = VersionRange.createFromVersion( artifact.getVersion() );
        }
        else
        {
            versionRange = artifact.getVersionRange();
        }

        /*
         * there's a bug with ArtifactFactory#createDependencyArtifact(String, String, VersionRange,
         * String, String, String) that ignores the scope parameter, that's why we use the one with
         * the extra null parameter
         */
        Artifact resolvedArtifact = m_factory.createDependencyArtifact( artifact.getGroupId(),
            artifact.getArtifactId(), versionRange, artifact.getType(), artifact.getClassifier(), artifact.getScope(),
            null );

        try
        {
            m_artifactResolver.resolve( resolvedArtifact, remoteRepositories, localRepository );
        }
        catch ( ArtifactResolutionException e )
        {
            throw new MojoExecutionException( "Error resolving artifact " + resolvedArtifact, e );
        }

        return resolvedArtifact;
    }
    
    /* BEGIN REMOTE ADDITION */
    
    private static final Pattern ALT_REPO_SYNTAX_PATTERN = Pattern.compile( "(.+)::(.+)::(.+)" );


    private void openRepositoryConnection( RemoteFileManager remoteFile ) throws MojoExecutionException
    {
        // use OBR specific deployment location?
        if ( obrDeploymentRepository != null )
        {
            altDeploymentRepository = obrDeploymentRepository;
        }

        if ( deploymentRepository == null && altDeploymentRepository == null )
        {
            String msg = "Deployment failed: repository element was not specified in the pom inside"
                + " distributionManagement element or in -DaltDeploymentRepository=id::layout::url parameter";

            throw new MojoExecutionException( msg );
        }

        if ( altDeploymentRepository != null )
        {
            getLog().info( "Using alternate deployment repository " + altDeploymentRepository );

            Matcher matcher = ALT_REPO_SYNTAX_PATTERN.matcher( altDeploymentRepository );
            if ( !matcher.matches() )
            {
                throw new MojoExecutionException( "Invalid syntax for alternative repository \""
                    + altDeploymentRepository + "\". Use \"id::layout::url\"." );
            }

            remoteFile.connect( matcher.group( 1 ).trim(), matcher.group( 3 ).trim() );
        }
        else
        {
            remoteFile.connect( deploymentRepository.getId(), deploymentRepository.getUrl() );
        }
    }
    
    /* END REMOTE ADDITION */


}
